package com.sky.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ser.Serializers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.context.BaseContext;
import com.sky.dto.*;
import com.sky.entity.*;
import com.sky.exception.AddressBookBusinessException;
import com.sky.exception.OrderBusinessException;
import com.sky.exception.ShoppingCartBusinessException;
import com.sky.mapper.*;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.utils.HttpClientUtil;
import com.sky.utils.WeChatPayUtil;
import com.sky.vo.OrderPaymentVO;
import com.sky.vo.OrderStatisticsVO;
import com.sky.vo.OrderSubmitVO;
import com.sky.vo.OrderVO;
import com.sky.websocket.WebSocketServer;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.ast.Or;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private AddressBookMapper addressBookMapper;

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    @Autowired
    private WeChatPayUtil weChatPayUtil;

    @Autowired
    private UserMapper userMapper;

    @Value("${sky.shop.address}")
    private String shopAddress;

    @Value("${sky.baidu.ak}")
    private String ak;

    @Autowired
    private WebSocketServer webSocketServer;


    /**
     * 检查客户的收货地址是否超出配送范围
     * @param address
     */
    private void checkOutOfRange(String address) {
        Map map = new HashMap();
        map.put("address",shopAddress);
        map.put("output","json");
        map.put("ak",ak);

        //获取店铺的经纬度坐标
        String shopCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);

        JSONObject jsonObject = JSON.parseObject(shopCoordinate);
        if(!jsonObject.getString("status").equals("0")){
            throw new OrderBusinessException("店铺地址解析失败");
        }

        //数据解析
        JSONObject location = jsonObject.getJSONObject("result").getJSONObject("location");
        String lat = location.getString("lat");
        String lng = location.getString("lng");
        //店铺经纬度坐标
        String shopLngLat = lat + "," + lng;

        map.put("address",address);
        //获取用户收货地址的经纬度坐标
        String userCoordinate = HttpClientUtil.doGet("https://api.map.baidu.com/geocoding/v3", map);

        jsonObject = JSON.parseObject(userCoordinate);
        if(!jsonObject.getString("status").equals("0")){
            throw new OrderBusinessException("收货地址解析失败");
        }

        //数据解析
        location = jsonObject.getJSONObject("result").getJSONObject("location");
        lat = location.getString("lat");
        lng = location.getString("lng");
        //用户收货地址经纬度坐标
        String userLngLat = lat + "," + lng;

        map.put("origin",shopLngLat);
        map.put("destination",userLngLat);
        map.put("steps_info","0");

        //路线规划
        String json = HttpClientUtil.doGet("https://api.map.baidu.com/directionlite/v1/driving", map);

        jsonObject = JSON.parseObject(json);
        if(!jsonObject.getString("status").equals("0")){
            throw new OrderBusinessException("配送路线规划失败");
        }

        //数据解析
        JSONObject result = jsonObject.getJSONObject("result");
        JSONArray jsonArray = (JSONArray) result.get("routes");
        Integer distance = (Integer) ((JSONObject) jsonArray.get(0)).get("distance");

        if(distance > 5000){
            //配送距离超过5000米
            throw new OrderBusinessException("超出配送范围");
        }
    }


    /**
     * 用户下单
     * @param ordersSubmitDTO
     * @return
     */
    @Transactional  //加一个事务注解 这样操作要么全成功 要么全都不执行 避免中断的问题
    //在@Transactional注解的方法中，事务只有在方法执行完成后才会提交。如果在方法执行过程中有插入操作，
    // 而后续的查询操作依赖于这些插入的数据，那么查询操作可能会因为插入数据未提交而无法获取到最新数据
    public OrderSubmitVO OrderSubmit(OrdersSubmitDTO ordersSubmitDTO) {
        //各种业务异常的处理 1.地址簿数据为空  2.购物车数据为空
        AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
        if(addressBook == null) {
            throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
        }

        //在生成具体订单之前 需要先检查用户的地址是否超出了配送范围 否则无法生成后续订单等
        checkOutOfRange(addressBook.getCityName() + addressBook.getDistrictName() +
                addressBook.getDetail());

        Long userId = BaseContext.getCurrentId();
        ShoppingCart shoppingCart = ShoppingCart.builder().userId(userId).build();
        List<ShoppingCart> shoppingCarts = shoppingCartMapper.Query(shoppingCart);
        if(shoppingCarts == null || shoppingCarts.size() == 0){
            throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
        }

        //向订单表插入一条数据
        Orders order = new Orders();
        BeanUtils.copyProperties(ordersSubmitDTO,order);
        order.setOrderTime(LocalDateTime.now());
        order.setPayStatus(order.UN_PAID);
        order.setStatus(order.PENDING_PAYMENT);
        order.setNumber(String.valueOf(System.currentTimeMillis()));
        order.setPhone(addressBook.getPhone());
        order.setConsignee(addressBook.getConsignee());
        order.setUserId(userId);

        orderMapper.insert(order);


        //向订单明细表插入N条数据
        List<OrderDetail> orderDetailList = new ArrayList<>();
        for (ShoppingCart cart : shoppingCarts) {
            OrderDetail orderDetail = new OrderDetail();
            BeanUtils.copyProperties(cart,orderDetail);
            orderDetail.setOrderId(order.getId());
            orderDetailList.add(orderDetail);
        }
        //接下来批量插入 这样可以减少数据库的访问次数
        orderDetailMapper.insertBatch(orderDetailList);

        //下单成功以后 清空用户的购物车数据
        shoppingCartMapper.DeleteCart(shoppingCart);

        //封装返回的VO结果
        OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
                .id(order.getId())
                .orderTime(order.getOrderTime())
                .orderNumber(order.getNumber())
                .orderAmount(order.getAmount())
                .build();

        return orderSubmitVO;
    }


    /**
     * 订单支付
     * 调用微信支付接口，生成预支付交易单 并不会真的支付（part1）
     * 我们修改的part2 本身是为了配合前端 绕过了小程序端的支付功能而存在的
     * part3是为了模拟由paySuccessNotify 支付成功后回调paySuccess 然后由paySuccess发起的更新管理端信息的功能
     * 开启part3可以补充完整paySuccess函数的对于前端发送订单消息的功能
     * @param ordersPaymentDTO
     * @return
     */
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.getById(userId);

        //调用微信支付接口，生成预支付交易单
        /**
         * part1
         * 下面这个部分为原始代码 新的代码为绕过了支付功能的部分 恢复则删除part2 恢复part1即可
         */
//        JSONObject jsonObject = weChatPayUtil.pay(
//                ordersPaymentDTO.getOrderNumber(), //商户订单号
//                new BigDecimal(0.01), //支付金额，单位 元
//                "苍穹外卖订单", //商品描述
//                user.getOpenid() //微信用户的openid
//        );
//
//        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
//            throw new OrderBusinessException("该订单已支付");
//        }
//
//        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
//        vo.setPackageStr(jsonObject.getString("package")); //理论上package里存储的应该是 prepay_id=xxxx  这个是预支付交易会话标识

        /**
         * part2
         * 绕过支付功能的部分 恢复则关闭part2 恢复part1即可
         */
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "ORDERPAID");
        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));

        //为替代微信支付成功后的数据库订单状态更新，多定义一个方法进行修改
        Integer OrderPaidStatus = Orders.PAID; //支付状态，已支付
        Integer OrderStatus = Orders.TO_BE_CONFIRMED;  //订单状态，待接单

        //发现没有将支付时间 check_out属性赋值，所以在这里更新
        LocalDateTime check_out_time = LocalDateTime.now();

        //获取订单号码
        String orderNumber = ordersPaymentDTO.getOrderNumber();

//        log.info("调用updateStatus，用于替换微信支付更新数据库状态的问题");
        orderMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, orderNumber);


        /**
         * part3
         * 模拟paySuccess函数中对于浏览器管理管推送消息
         * 由于小程序端的支付功能被绕过 所以无法由微信端主动发起调用
         */
        Map map = new HashMap();
        map.put("type",1);//1表示来单提醒 2表示客户催单
        Orders order = orderMapper.getByNumber(orderNumber); //多查一次数据库
        map.put("orderId",order.getId());
        map.put("content","订单号:" + orderNumber);

        String json = JSON.toJSONString(map);
        log.info("具体信息--->{}",json);
        webSocketServer.sendToAllClient(json);

        //不论part1还是2 这个部分不变
        return vo;
    }

    /**
     * 支付成功，修改订单状态
     *
     * @param outTradeNo 表示相应的订单号信息
     */
    public void paySuccess(String outTradeNo) {

        // 根据订单号查询订单
        Orders ordersDB = orderMapper.getByNumber(outTradeNo);

        // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
        Orders orders = Orders.builder()
                .id(ordersDB.getId())
                .status(Orders.TO_BE_CONFIRMED)
                .payStatus(Orders.PAID)
                .checkoutTime(LocalDateTime.now())
                .build();

        orderMapper.update(orders);

        //通过websocket向浏览器管理管推送消息 type orderId content
        Map map = new HashMap();
        map.put("type",1);//1表示来单提醒 2表示客户催单
        map.put("orderId",ordersDB.getId());
        map.put("content","订单号:" + outTradeNo);

        String json = JSON.toJSONString(map);
        webSocketServer.sendToAllClient(json);
    }

    /**
     * 查询历史订单 用户端分页查询
     * @param page
     * @param pageSize
     * @param status
     * @return
     */
    public PageResult QueryHistoryOrder(int page, int pageSize, Integer status) {
        //PageHelper会自动设置拦截器 拦截即将执行的SQL语句 并检查是否有分页参数 如果有就自动把分页的参数注入进去
        // 也就是sql语句自动加limit等条件
        //执行完sql以后 会把结果封装到Page对象 也就是那个执行sql的调用方那里
        PageHelper.startPage(page, pageSize);//设置分页
        OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO();
        ordersPageQueryDTO.setUserId(BaseContext.getCurrentId());
        ordersPageQueryDTO.setStatus(status);
        //通过条件查询 可以查到以Page封装的Orders的数据
        Page<Orders> pages = orderMapper.pageQuery(ordersPageQueryDTO);

        //OrderVO继承了Orders类 所有具有其所有的内容 多一个orderDetailList而已
        List<OrderVO> list = new ArrayList<>(); //封装一个list 专门用于存储OrderVO

        //说明确实查询到的数据不为空
        if(pages != null && pages.getTotal() > 0 ){
            for (Orders orders : pages) {
                OrderVO orderVO = new OrderVO();
                //先把基础的数值赋值过来
                BeanUtils.copyProperties(orders,orderVO);
                //赋值完毕以后还差一个orderDetailList 需要通过查询获得结果
                List<OrderDetail> orderDetailList = orderDetailMapper.QueryOrderDetailsByOrderId(orders.getId());
                orderVO.setOrderDetailList(orderDetailList);
                list.add(orderVO);
            }
        }

        return new PageResult(pages.getTotal(),list);
    }


    /**
     * 用户端-查询订单详情
     * @param id
     * @return
     */
    public OrderVO QueryOrderDetail(Long id) {
        Orders order = orderMapper.getByOrder_Id(id);
        OrderVO orderVO = new OrderVO();
        BeanUtils.copyProperties(order,orderVO);
        List<OrderDetail> orderDetailList = orderDetailMapper.QueryOrderDetailsByOrderId(id);
        orderVO.setOrderDetailList(orderDetailList);
        return orderVO;
    }


    /**
     * 取消订单-用户端
     * @param id
     */
    public void CancelOrder(Long id) throws Exception {

        //统一查询订单的状态参数
//        Integer status = orderMapper.getStatusById(id);
        //统一 先检查这个订单是否存在
        Orders orders = orderMapper.getByOrder_Id(id);
        if(orders == null){
            //订单不存在的情况
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }
        Integer status = orders.getStatus();

        if(status.equals(Orders.PENDING_PAYMENT) || status.equals(Orders.TO_BE_CONFIRMED)){
            //1.如果订单是待支付 或者 待接单 则可以直接取消 修改相应的参数即可
            Orders orders2 = new Orders(); //订单模板 用于更新订单的细节
            orders2.setId(orders.getId());
            //3.待支付的状态需要给用户退款
            if(orders.getStatus().equals(Orders.TO_BE_CONFIRMED)){
                weChatPayUtil.refund(orders.getNumber(), orders.getNumber(),
                        orders.getAmount(), orders.getAmount());
            }
            //4.取消完毕 统一修改订单状态为已取消
            orders2.setStatus(Orders.CANCELLED);
            orders2.setPayStatus(Orders.REFUND);
            orders2.setCancelReason(Orders.DefaultCancelReason);
            orders2.setCancelTime(LocalDateTime.now());
            orderMapper.update(orders2);

        }else{
            //2.如果订单状态是已接单 派送中 那么需要电话和商家沟通 所有其他的订单状态
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }

    }


    /**
     * 再来一单接口
     * @param id 此id为订单id
     */
    public void RepetitionOrder(Long id) {
        //业务逻辑：将原订单中的商品重新加入到购物车
        //通过订单id 去订单表查出用户id 后续要用
//        Long user_id = orderMapper.getByOrder_Id(id).getId();
//        Long user_id = orderMapper.getByOrder_Id(id).getUserId();
        Long user_id = BaseContext.getCurrentId();

        //1.首先根据订单id 去订单细节表中找出所有的list项目
        List<OrderDetail> orderDetailList = orderDetailMapper.QueryOrderDetailsByOrderId(id);

        //2.循环 根据相应的菜品id和套餐id查询 将结果封装为shoppingcart对象（没必要挨个查数据库 shoppingcart只需要相应的菜品和套餐id即可）
        //核心：将订单详情对象转换为购物车对象
        //使用StreamAPI流式操作 这样可以减少对数据库的访问
        List<ShoppingCart> shoppingCartList = orderDetailList.stream().map(x->{
            ShoppingCart shoppingCart = new ShoppingCart();
            BeanUtils.copyProperties(x,shoppingCart,"id");//将x中的变量赋值给购物车 除了id属性
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCart.setUserId(user_id);
            return shoppingCart;
        }).collect(Collectors.toList());//将流中所有的购物车对象收集到list中 生成购物车

        //3.统一调用mapper将内容插入到购物车
        shoppingCartMapper.insertBatch(shoppingCartList);
    }


    /**
     * 管理端
     * 订单条件查询接口
     * @param ordersPageQueryDTO
     * @return
     */
    public PageResult ConditionalSearch(OrdersPageQueryDTO ordersPageQueryDTO) {

        PageHelper.startPage(ordersPageQueryDTO.getPage(),ordersPageQueryDTO.getPageSize());

        //管理端查询 不需要设置相应的用户id 管理员可以查看所有的订单信息
//        ordersPageQueryDTO.setUserId(BaseContext.getCurrentId());

        Page<Orders> orders = orderMapper.pageQuery(ordersPageQueryDTO);

        //我们最终需要的是List<OrderVO> 所以接下来需要去完善这部分
        //每个orderVO都直接拷贝orders的内容 同时结合特例 本接口需要orderDishes字段
        long total = orders.getTotal();
        List<Orders> result = orders.getResult();
        List<OrderVO> orderVOS = new ArrayList<>();

        //根据条件去查询 查询到的内容非空 再去进行后续封装和处理 否则直接返回空结果即可
        if(!CollectionUtils.isEmpty(result)){
            for (Orders orders1 : result) {
                OrderVO orderVO = new OrderVO();
                BeanUtils.copyProperties(orders1,orderVO);
                //接下来需要处理核心字段 orderDishes 需要去查询orderDetail表 将每一个detail处理成字符串的形式 所以单独弄一个函数出来
                String orderDishes = getOrderDishes(orders1.getId());
                orderVO.setOrderDishes(orderDishes);
                orderVOS.add(orderVO);
            }
        }

        return new PageResult(total,orderVOS);


    }


    /**
     * 统计各个状态订单的数量
     * @return
     */
    public OrderStatisticsVO CountOrderStatistics() {
        Integer confirmed = orderMapper.CountNumberByStatus(Orders.CONFIRMED);
        Integer deliveryInProgress = orderMapper.CountNumberByStatus(Orders.DELIVERY_IN_PROGRESS);
        Integer toBeConfirmed = orderMapper.CountNumberByStatus(Orders.TO_BE_CONFIRMED);
        OrderStatisticsVO orderStatisticsVO = OrderStatisticsVO.builder().confirmed(confirmed)
                .deliveryInProgress(deliveryInProgress)
                .toBeConfirmed(toBeConfirmed)
                .build();
        return orderStatisticsVO;
    }


    /**
     * 管理端-商家接单
     * @param ordersConfirmDTO
     */
    public void AcceptOrder(OrdersConfirmDTO ordersConfirmDTO) {
        Long id = ordersConfirmDTO.getId();
        Orders orders = Orders.builder()
                .id(id)
                .status(Orders.CONFIRMED)
                .build();
        orderMapper.update(orders);
    }

    /**
     * 管理端-商家拒单
     * @param ordersRejectionDTO
     * @return
     */
    public void RejectOrder(OrdersRejectionDTO ordersRejectionDTO) throws Exception {
        //1.首先根据id查询具体的订单
        Orders order = orderMapper.getByOrder_Id(ordersRejectionDTO.getId());

        //2.只有订单确实存在 且订单的状态为待接单才可以
        if(order == null || !order.getStatus().equals(Orders.TO_BE_CONFIRMED)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }

        //3.如果订单已经支付 我们需要单独处理一下退款
        //我们由于支付功能关闭 所以默认不退款
//        if(order.getPayStatus().equals(Orders.PAID)){
//            String refund = weChatPayUtil.refund(
//                    order.getNumber(),
//                    order.getNumber(),
//                    new BigDecimal(0.01),
//                    new BigDecimal(0.01));
//            log.info("申请退款:{}",refund);
//        }

        //创建更新后的订单信息 id指定 原因指定 修改订单的status等
        Orders orders = Orders.builder()
                .id(ordersRejectionDTO.getId())
                .rejectionReason(ordersRejectionDTO.getRejectionReason())
                .status(Orders.CANCELLED)
                .cancelTime(LocalDateTime.now())
                .build();
        orderMapper.update(orders);

    }

    /**
     * 管理端-订单取消
     * @param ordersCancelDTO
     */
    public void CancalOrder(OrdersCancelDTO ordersCancelDTO) throws Exception {
        //1.首先根据id查询具体的订单
        Orders order = orderMapper.getByOrder_Id(ordersCancelDTO.getId());

        //2.只有订单确实存在才可以
        if(order == null){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }

        //3.如果订单已经支付则需要退款
        Integer payStatus = order.getPayStatus();
//        if(payStatus.equals(Orders.PAID)){
//            String refund = weChatPayUtil.refund(
//                    order.getNumber(),
//                    order.getNumber(),
//                    new BigDecimal(0.01),
//                    new BigDecimal(0.01));
//            log.info("申请退款:{}",refund);
//        }

        //4.更新订单信息即可
        Orders orders = Orders.builder()
                .id(ordersCancelDTO.getId())
                .status(Orders.CANCELLED)
                .cancelReason(ordersCancelDTO.getCancelReason())
                .cancelTime(LocalDateTime.now())
                .build();

        orderMapper.update(orders);


    }


    /**
     * 派送订单
     * @param id
     */
    public void DeliveryOrder(Long id) {
        //1.查询订单 订单存在才行 且订单状态必须为待派送
        Orders order = orderMapper.getByOrder_Id(id);
        if(order == null || !order.getStatus().equals(Orders.CONFIRMED)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }

        //2.修改订单状态并且更新即可
        Orders orders = Orders.builder()
                .id(id)
                .status(Orders.DELIVERY_IN_PROGRESS)
                .build();

        orderMapper.update(orders);
    }

    /**
     * 管理端-订单完成接口
     * @param id
     */
    public void CompleteOrder(Long id) {

        Orders order = orderMapper.getByOrder_Id(id);

        if(order == null){
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }
        if(!order.getStatus().equals(Orders.DELIVERY_IN_PROGRESS)){
            throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
        }

        Orders orders = Orders.builder()
                .id(id)
                .status(Orders.COMPLETED)
                .build();

        orderMapper.update(orders);

    }

    /**
     * 用户端-客户催单
     * @param id order_id
     */
    public void ReminderOrder(Long id) {
        Orders order = orderMapper.getByOrder_Id(id);

        if(order == null){
            throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
        }

        //通过websocket向浏览器管理管推送消息 type orderId content
        Map map = new HashMap();
        map.put("type",2);//1表示来单提醒 2表示客户催单
        map.put("orderId",order.getId());
        map.put("content","订单号:" + order.getNumber());

        String json = JSON.toJSONString(map);
        webSocketServer.sendToAllClient(json);

    }


    /**
     * 根据订单id查询订单明细表 将查询结果组成字符串返回
     * @param orderId
     * @return
     */
    public String getOrderDishes(Long orderId){

        List<OrderDetail> orderDetailList = orderDetailMapper.QueryOrderDetailsByOrderId(orderId);

        //既然已经查询到了订单 则订单的菜品肯定不可能为空 所以不需要验证
        List<String> orderDishList = orderDetailList.stream().map(x->{
            String orderDish = x.getName() + "*" + x.getNumber() + ";";
            return orderDish;
        }).collect(Collectors.toList());

        //String.join("", orderDishList) 方法会将这些字符串按顺序连接起来，
        // 中间不添加任何分隔符（因为传入的第一个参数是空字符串 ""）
        //最终返回的结果是一个完整的字符串，包含了所有订单菜品的信息
        return String.join("",orderDishList);

    }


}

















